ESLint Plugin Kit
Description
A collection of utilities to help build ESLint plugins.
Installation
For Node.js and compatible runtimes:
npm install @eslint/plugin-kit
# or
yarn add @eslint/plugin-kit
# or
pnpm install @eslint/plugin-kit
# or
bun install @eslint/plugin-kit
For Deno:
deno add @eslint/plugin-kit
Usage
This package exports the following utilities:
ConfigCommentParser
- used to parse ESLint configuration comments (i.e., /* eslint-disable rule */
)VisitNodeStep
and CallMethodStep
- used to help implement SourceCode#traverse()
Directive
- used to help implement SourceCode#getDisableDirectives()
TextSourceCodeBase
- base class to help implement the SourceCode
interface
To use the ConfigCommentParser
class, import it from the package and create a new instance, such as:
import { ConfigCommentParser } from "@eslint/plugin-kit";
const commentParser = new ConfigCommentParser();
const directive = commentParser.parseDirective(
"eslint-disable prefer-const, semi -- I don't want to use these.",
);
if (directive) {
console.log(directive.label);
console.log(directive.value);
console.log(directive.justification);
}
There are different styles of directive values that you'll need to parse separately to get the correct format:
import { ConfigCommentParser } from "@eslint/plugin-kit";
const commentParser = new ConfigCommentParser();
const list = commentParser.parseListConfig("prefer-const, semi");
console.log(Object.entries(list));
const strings = commentParser.parseStringConfig("foo:off, bar");
console.log(Object.entries(strings));
const jsonLike = commentParser.parseJSONLikeConfig(
"semi:[error, never], prefer-const: warn",
);
console.log(Object.entries(jsonLike.config));
VisitNodeStep
and CallMethodStep
The VisitNodeStep
and CallMethodStep
classes represent steps in the traversal of source code. They implement the correct interfaces to return from the SourceCode#traverse()
method.
The VisitNodeStep
class is the more common of the two, where you are describing a visit to a particular node during the traversal. The constructor accepts three arguments:
target
- the node being visited. This is used to determine the method to call inside of a rule. For instance, if the node's type is Literal
then ESLint will call a method named Literal()
on the rule (if present).phase
- either 1 for enter or 2 for exit.args
- an array of arguments to pass into the visitor method of a rule.
For example:
import { VisitNodeStep } from "@eslint/plugin-kit";
class MySourceCode {
traverse() {
const steps = [];
for (const { node, parent, phase } of iterator(this.ast)) {
steps.push(
new VisitNodeStep({
target: node,
phase: phase === "enter" ? 1 : 2,
args: [node, parent],
}),
);
}
return steps;
}
}
The CallMethodStep
class is less common and is used to tell ESLint to call a specific method on the rule. The constructor accepts two arguments:
target
- the name of the method to call, frequently beginning with "on"
such as "onCodePathStart"
.args
- an array of arguments to pass to the method.
For example:
import { VisitNodeStep, CallMethodStep } from "@eslint/plugin-kit";
class MySourceCode {
traverse() {
const steps = [];
for (const { node, parent, phase } of iterator(this.ast)) {
steps.push(
new VisitNodeStep({
target: node,
phase: phase === "enter" ? 1 : 2,
args: [node, parent],
}),
);
steps.push(
new CallMethodStep({
target: "onIteration",
args: [steps.length]
});
)
}
return steps;
}
}
Directive
The Directive
class represents a disable directive in the source code and implements the Directive
interface from @eslint/core
. You can tell ESLint about disable directives using the SourceCode#getDisableDirectives()
method, where part of the return value is an array of Directive
objects. Here's an example:
import { Directive, ConfigCommentParser } from "@eslint/plugin-kit";
class MySourceCode {
getDisableDirectives() {
const directives = [];
const problems = [];
const commentParser = new ConfigCommentParser();
this.getInlineConfigNodes().forEach(comment => {
const { label, value, justification } =
commentParser.parseDirective(comment.value);
switch (label) {
case "eslint-disable":
case "eslint-enable":
case "eslint-disable-next-line":
case "eslint-disable-line": {
const directiveType = label.slice("eslint-".length);
directives.push(
new Directive({
type: directiveType,
node: comment,
value,
justification,
}),
);
}
}
});
return {
directives,
problems,
};
}
}
TextSourceCodeBase
The TextSourceCodeBase
class is intended to be a base class that has several of the common members found in SourceCode
objects already implemented. Those members are:
lines
- an array of text lines that is created automatically when the constructor is called.getLoc(node)
- gets the location of a node. Works for nodes that have the ESLint-style loc
property and nodes that have the Unist-style position
property. If you're using an AST with a different location format, you'll still need to implement this method yourself.getRange(node)
- gets the range of a node within the source text. Works for nodes that have the ESLint-style range
property and nodes that have the Unist-style position
property. If you're using an AST with a different range format, you'll still need to implement this method yourself.getText(nodeOrToken, charsBefore, charsAfter)
- gets the source text for the given node or token that has range information attached. Optionally, can return additional characters before and after the given node or token. As long as getRange()
is properly implemented, this method will just work.getAncestors(node)
- returns the ancestry of the node. In order for this to work, you must implement the getParent()
method yourself.
Here's an example:
import { TextSourceCodeBase } from "@eslint/plugin-kit";
export class MySourceCode extends TextSourceCodeBase {
#parents = new Map();
constructor({ ast, text }) {
super({ ast, text });
}
getParent(node) {
return this.#parents.get(node);
}
traverse() {
const steps = [];
for (const { node, parent, phase } of iterator(this.ast)) {
this.#parent.set(node, parent);
steps.push(
new VisitNodeStep({
target: node,
phase: phase === "enter" ? 1 : 2,
args: [node, parent],
}),
);
}
return steps;
}
}
In general, it's safe to collect the parent information during the traverse()
method as getParent()
and getAncestor()
will only be called from rules once the AST has been traversed at least once.
License
Apache 2.0
The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. Become a Sponsor
to get your logo on our READMEs and website.
Technology sponsors allow us to use their products and services for free as part of a contribution to the open source ecosystem and our work.